/*
 *
 *  This file is part of JSmex.
 *
 *  JSmex is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  JSmex is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Foobar; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package jsmex.cardservice;


import java.util.Enumeration;
import java.util.Vector;
import opencard.core.service.CardType;
import opencard.core.service.CardServiceScheduler;
import opencard.core.service.CardServiceFactory;
import opencard.core.terminal.CardID;
import opencard.core.terminal.CardTerminalException;
import opencard.core.terminal.CommandAPDU;
import opencard.core.terminal.ResponseAPDU;
import opencard.core.service.CardChannel;




/**
 * @author Tobias Senger (jsmex@t-senger.de)
 *
 */
public class JSmexCardServiceFactory extends CardServiceFactory{
    
    public JSmexCardServiceFactory() {
        super();
    }
    
    private static Vector gsm_classes;
    private static Vector cryptoflex_classes;
    private static Vector geldkarte_classes;
    private static Vector usim_classes;
    private static Vector gp_classes;
    private static Vector mrtd_classes;
        
    static {
        gsm_classes = new Vector();
        gsm_classes.addElement(jsmex.cardservice.GsmCardService.class);
        
        cryptoflex_classes = new Vector();
        cryptoflex_classes.addElement(jsmex.cardservice.PKCS15CardService.class);
        
        geldkarte_classes = new Vector();
        geldkarte_classes.addElement(jsmex.cardservice.GeldkarteCardService.class);
        
        usim_classes = new Vector();
        usim_classes.addElement(jsmex.cardservice.UsimCardService.class);
        
        gp_classes = new Vector();
        gp_classes.addElement(jsmex.cardservice.GlobalPlatformCardService.class);
        
        mrtd_classes = new Vector();
        mrtd_classes.addElement(jsmex.cardservice.MRTDCardService.class);
        
    }
    
    
    /**
     *
     * @param type
     * @return Returns a vector with CardService classes
     */
    protected Enumeration getClasses(CardType type) {
        
        switch (type.getType()) {
            case 1: return gsm_classes.elements();
            case 2: return geldkarte_classes.elements();
            case 3: return cryptoflex_classes.elements();
            case 4: return usim_classes.elements();
            case 5: return gp_classes.elements();
            case 6: return mrtd_classes.elements();
            default: return null;
        }
    }
    
    /**
     * This method sends the APDU which comes in argument cmd to the card and returns a Response APDU.
     * @param css
     * @param cmd This parameter contains the APDU which will be send to the card.
     * @return This method retruns the Response APDU from the card.
     */
    private ResponseAPDU sendCmd(CardServiceScheduler css, byte[] cmd) throws CardTerminalException {
        
        CommandAPDU command = new CommandAPDU(25);
        CardChannel cs = null;
        ResponseAPDU response;
        
        command.setLength(0);
        command.append(cmd);
        
        try {
            cs = css.allocateCardChannel(this,true);
            response = cs.sendCommandAPDU(command);
        } finally {
            css.releaseCardChannel(cs);
        }
        return response;
    }
    
    /**
     * This method send some APDU to the card to see if it is a PKCS#15 compatible smartcard.
     * @param css
     * @return Returns 'true' if a PKCS#15 compatible smartcard was found.
     */
    private boolean isPKCS15Card(CardServiceScheduler css) throws CardTerminalException{
        
        byte[] cmd = { (byte)0xC0, (byte)0xA4, (byte)0x00, (byte)0x00, (byte)0x02, (byte)0x2F, (byte)0x00 }; // Select EF_DIR command
        byte[] cmd2 = { (byte)0xC0, (byte)0xC0, (byte)0x00, (byte)0x00 };
        byte[] cmd3 = { (byte)0xC0, (byte)0xB0, (byte)0x00, (byte)0x00 };
        byte[] PKCS15_AID = { (byte)0xA0, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x63, (byte)0x50, (byte)0x4B, (byte)0x43, (byte)0x53, (byte)0x2D, (byte)0x31, (byte)0x35 };
        
        CommandAPDU command = new CommandAPDU(25);
        CardChannel cc = null;
        ResponseAPDU response;
        
        command.setLength(0);
        command.append(cmd);
        
        try {
            cc = css.allocateCardChannel(this,true);
            response = cc.sendCommandAPDU(command);
            command.setLength(0);
            command.append(cmd2);
            command.append(response.sw2());
            response = cc.sendCommandAPDU(command);
            command.setLength(0);
            command.append(cmd3);
            command.append((byte)response.getByte(3));
            response = cc.sendCommandAPDU(command);
        } finally {
            css.releaseCardChannel(cc);
        }
        for (int i=0;i<=response.getLength();i++) {
            if (response.getByte(i)==(byte)0x61) {
                for (int j=i;j<=response.getLength();j++) {
                    if (response.getByte(j)==(byte)0x4F) {
                        for (int k=0;k<=11;k++) {
                            if ((byte)response.getByte(j+2+k)!=PKCS15_AID[k]) return false;
                        }
                        System.out.println("Found a PKCS15_AID");
                        return true;
                    }
                }
            }
        }
        return false;
    }
    
    /**
     * This method send some APDU to the card to see if it is a UICC with an USIM application.
     * @param css
     * @return Returns 'true' if a USIM application  was found.
     */
    private boolean isUSIMCard(CardServiceScheduler css) throws CardTerminalException {
        
        byte[] USIM_AID = { (byte)0xA0, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x87, (byte)0x10, (byte)0x02, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0x89, (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x00};
        byte[] cmd = { (byte)0x00, (byte)0xA4, (byte)0x00, (byte)0x04, (byte)0x02, (byte)0x3F, (byte)0x00, (byte)0x00}; // Select MF command
        byte[] cmd2 = { (byte)0x00, (byte)0xA4, (byte)0x00, (byte)0x04, (byte)0x02, (byte)0x2F, (byte)0x00, (byte)0x00}; // Select EF_DIR command
        byte[] cmd3 = { (byte)0x00, (byte)0xB2, (byte)0x01, (byte)0x04, (byte)0x00};
        
        CardChannel cc = null;
        
        ResponseAPDU resp = null;
        try {
            cc = css.allocateCardChannel(this,true);
            resp = cc.sendCommandAPDU(new CommandAPDU(cmd));
            resp = cc.sendCommandAPDU(new CommandAPDU(cmd2));
            resp = cc.sendCommandAPDU(new CommandAPDU(cmd3));
            
        } finally {
            css.releaseCardChannel(cc);
        }
        
        for (int i=0;i<=resp.getLength();i++) {
            if (resp.getByte(i)==(byte)0x61) {
                for (int j=i;j<=resp.getLength();j++) {
                    if (resp.getByte(j)==(byte)0x4F) {
                        for (int k=0;k<USIM_AID.length;k++) {
                            if ((byte)resp.getByte(j+2+k)!=USIM_AID[k]) return false;
                        }
                        return true;
                    }
                }
            }
        }
        return false;
    }
    
    /**
     * This method send some APDU to the card to see if it is a Global Platform compatible smartcard.
     * @param css
     * @return Returns 'true' if a a Global Platform compatible smartcard was found.
     */
    private boolean isGlobalPlatformCard(CardServiceScheduler css) throws CardTerminalException{
        
        byte[] cmd = { (byte)0x00, (byte)0xA4, (byte)0x04, (byte)0x00, (byte)0x08}; // Select EF_DIR command
        byte[] CardManager_AID = { (byte)0xA0, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x00};
        
        CommandAPDU command = new CommandAPDU(15);
        CardChannel cc = null;
        ResponseAPDU response;
        
        command.setLength(0);
        command.append(cmd);
        command.append(CardManager_AID);
        command.append((byte)0x00);
        
        try {
            cc = css.allocateCardChannel(this,true);
            response = cc.sendCommandAPDU(command);
        } finally {
            css.releaseCardChannel(cc);
        }
        
        if (response.sw()==0x9000) return true;
        else return false;
    }
    
    
    
    protected CardType getCardType(CardID cid, CardServiceScheduler css) throws CardTerminalException {
        
        byte[] atr = cid.getATR();
        byte[] gsmcmd = { (byte)0xA0, (byte)0xA4, (byte)0x00, (byte)0x00, (byte)0x02, (byte)0x7F, (byte)0x10 }; //Select FID DF_Telecom for GSMCard
        byte[] gkcmd = { (byte)0x00, (byte)0xA4, (byte)0x04, (byte)0x00, (byte)0x09, (byte)0xD2,  (byte)0x76, (byte)0x00, (byte)0x00, (byte)0x25, (byte)0x45, (byte)0x50, (byte)0x02, (byte)0x00, (byte)0x00}; //Select DF_BÖRSE_AID command for Geldkarte
        byte[] mrtdcmd = { (byte)0x00, (byte)0xA4, (byte)0x04, (byte)0x0C, (byte)0x07, (byte)0xA0,  (byte)0x00, (byte)0x00, (byte)0x02, (byte)0x47, (byte)0x10, (byte)0x01}; //Select MRTD_AID command
        
        
        
        /*
         * if (sendCmd(css,mrtdcmd).sw1() == (byte)0x90) return new CardType(6);
         * use a other method to ensure that a mrtd is present
         * for testing purpose we return everytime a mrtd-cardservice
         */
//        sendCmd(css,mrtdcmd);
//        return new CardType(6);
        if (sendCmd(css,mrtdcmd).sw1() == (byte)0x90) return new CardType(6);
        if (isUSIMCard(css)) return new CardType(4);
        if (sendCmd(css,gsmcmd).sw1() == (byte)0x9F) return new CardType(1);
        if (sendCmd(css,gkcmd).sw1() == (byte)0x90) return new CardType(2);
        if (isPKCS15Card(css)) return new CardType(3);
        if (isGlobalPlatformCard(css)) return new CardType(5);
        
        
        return CardType.UNSUPPORTED;
    }
}
